-- 
-- Please see the readme.txt file included with this distribution for 
-- attribution and copyright information.
--

--
--	GENERAL
--

function getActiveInit()
	local nActiveInit = nil;
	
	local nodeActive = getActiveCT();
	if nodeActive then
		nActiveInit = NodeManager.get(nodeActive, "initresult", 0);
	end
	
	return nActiveInit;
end

--
--  NODE TRANSLATION
--

function getActiveCT()
	-- FIND TRACKER NODE
	local nodeTracker = DB.findNode("combattracker");
	if not nodeTracker then
		return nil;
	end

	-- LOOK FOR ACTIVE NODE
	for keyEntry, nodeEntry in pairs(nodeTracker.getChildren()) do
		if NodeManager.get(nodeEntry, "active", 0) == 1 then
			return nodeEntry;
		end
	end

	-- IF NO ACTIVE NODES, THEN RETURN NIL
	return nil;
end

function getCTFromNode(varNode)
	-- SETUP
	local sNode = "";
	if type(varNode) == "string" then
		sNode = varNode;
	elseif type(varNode) == "databasenode" then
		sNode = varNode.getNodeName();
	else
		return nil;
	end
	
	-- FIND TRACKER NODE
	local nodeTracker = DB.findNode("combattracker");
	if not nodeTracker then
		return nil;
	end

	-- Check for exact CT match
	for keyEntry, nodeEntry in pairs(nodeTracker.getChildren()) do
		if nodeEntry.getNodeName() == sNode then
			return nodeEntry;
		end
	end

	-- Otherwise, check for link match
	for keyEntry, nodeEntry in pairs(nodeTracker.getChildren()) do
		local nodeLink = nodeEntry.getChild("link");
		if nodeLink then
			local sRefClass, sRefNode = nodeLink.getValue();
			if sRefNode == sNode then
				return nodeEntry;
			end
		end
	end

	return nil;	
end


--
-- DROP HANDLING
--

function onDrop(nodetype, nodename, draginfo)
	local rSourceActor, rTargetActor = getDropActors(nodetype, nodename, draginfo);
	if rTargetActor then
		local dragtype = draginfo.getType();

		-- FACTION CHANGES
		if dragtype == "combattrackerff" and User.isHost() then
			NodeManager.set(rTargetActor.nodeCT, "friendfoe", "string", draginfo.getStringData());
			return true;
		end

		-- TARGETING
		if dragtype == "targeting" and User.isHost() then
			onTargetingDrop(rSourceActor, rTargetActor, draginfo);
			return true;
		end

		-- ATTACK ROLLS
		if dragtype == "attack" then
			onAttackClassDrop(rSourceActor, rTargetActor, draginfo);
			return true;
		end

		-- DAMAGE ROLLS
		if dragtype == "damage" then
			onDamageClassDrop(rSourceActor, rTargetActor, draginfo);
			return true;
		end

		-- EFFECTS
		if dragtype == "effect" then
			onEffectDrop(rSourceActor, rTargetActor, draginfo);
			return true;
		end

		-- NUMBER DROPS
		if dragtype == "number" then
			onNumberDrop(rSourceActor, rTargetActor, draginfo);
			return true;
		end
	end
end

function onNumberDrop(rSourceActor, rTargetActor, draginfo)
	-- CHECK FOR ATTACK RESULTS
	if string.match(draginfo.getDescription(), "%[ATTACK") then
		onAttackResultDrop(rSourceActor, rTargetActor, draginfo);
		return;
	end

	-- CHECK FOR DAMAGE RESULTS
	if string.match(draginfo.getDescription(), "%[DAMAGE") then
		onDamageResultDrop(rSourceActor, rTargetActor, draginfo, 0);
		return;
	end
	
	-- CHECK FOR HEALING RESULTS
	if string.match(draginfo.getDescription(), "%[HEAL") then
		if string.match(draginfo.getDescription(), "%[TEMP") then
			onDamageResultDrop(rSourceActor, rTargetActor, draginfo, 2);
		else
			onDamageResultDrop(rSourceActor, rTargetActor, draginfo, 1);
		end
		return;
	end

	-- CHECK FOR SAVING THROWS
	if string.match(draginfo.getDescription(), "%[SAVE") then
		onSaveResultDrop(rSourceActor, rTargetActor, draginfo);
		return;
	end
	
	-- CHECK FOR EFFECTS
	if string.match(draginfo.getDescription(), "%[EFFECT") then
		onEffectDrop(rSourceActor, rTargetActor, draginfo);
		return;
	end
end

function getDropActors(nodetype, nodename, draginfo)
	local rSourceActor = getActionActors(draginfo);
	local rTargetActor = getActor(nodetype, nodename);
	return rSourceActor, rTargetActor;
end

function buildCustomRollArray(rSourceActor, rTargetActor)
	-- SETUP
	local custom = {};

	-- ENCODE SOURCE ACTOR (CT, PC, NPC)
	if rSourceActor then
		local sSourceType = "ct";
		local nodeSource = rSourceActor.nodeCT;
		if not nodeSource then
			if rSourceActor.sType == "pc" then
				sSourceType = "pc";
			elseif rSourceActor.sType == "npc" then
				sSourceType = "npc";
			end
			nodeSource = rSourceActor.nodeCreature;
		end
		if nodeSource then
			custom[sSourceType] = nodeSource;
		end
	end
	
	-- ENCODE TARGET ACTOR (CT, PC) (NO NPC TARGETING)
	if rTargetActor then
		local sSourceType = "targetct";
		local nodeSource = rTargetActor.nodeCT;
		if not nodeSource then
			if rTargetActor.sType == "pc" then
				sSourceType = "targetpc";
				nodeSource = rSourceActor.nodeCreature;
			end
		end
		if nodeSource then
			custom[sSourceType] = nodeSource.getNodeName();
		end
	end

	-- RESULTS
	return custom;
end

function onTargetingDrop(rSourceActor, rTargetActor, draginfo)
	if rTargetActor.nodeCT then
		-- ADD CREATURE TARGET
		if rSourceActor then
			if rSourceActor.nodeCT then
				TargetingManager.addTarget("host", rSourceActor.sCTNode, rTargetActor.sCTNode);
			end

		-- ADD EFFECT TARGET
		else
			local sRefClass, sRefNode = draginfo.getShortcutData();
			if sRefClass and sRefNode then
				if sRefClass == "combattracker_effect" then
					TargetingManager.addTarget("host", sRefNode, rTargetActor.sCTNode);
				end
			end
		end
	end
end

function onAttackClassDrop(rSourceActor, rTargetActor, draginfo)
	if User.isHost() or not OptionsManager.isOption("PATK", "off") then
		RulesManager.dclkAction("attack", draginfo.getNumberData(), draginfo.getDescription(), rSourceActor, rTargetActor, nil, true);
	end
end

function onDamageClassDrop(rSourceActor, rTargetActor, draginfo)
	if User.isHost() or not OptionsManager.isOption("PDMG", "off") then
		local dice = {};
		if draginfo.getDieList() then
			for k, v in pairs(draginfo.getDieList()) do
				table.insert(dice, v["type"]);
			end
		end
		RulesManager.dclkAction("damage", draginfo.getNumberData(), draginfo.getDescription(), rSourceActor, rTargetActor, dice, true);
	end
end

function onSaveClassDrop(rSourceActor, rTargetActor, draginfo)
	if not User.isHost() then
		local bOwned = false;
		if rTargetActor.sType == "pc" and rTargetActor.nodeCreature then
			bOwned = rTargetActor.nodeCreature.isOwner();
		end
		if not bOwned then
			ChatManager.SystemMessage("[ERROR] Saving throw dropped on target which you do not control.");
			return;
		end
	end

	RulesManager.dclkAction("save", draginfo.getNumberData(), draginfo.getDescription(), rTargetActor, nil, dice, true);
end

function onSaveResultDrop(rSourceActor, rTargetActor, draginfo)
	if not User.isHost() then
		local bOwned = false;
		if rTargetActor.sType == "pc" and rTargetActor.nodeCreature then
			bOwned = rTargetActor.nodeCreature.isOwner();
		end
		if not bOwned then
			ChatManager.SystemMessage("[ERROR] Saving throw dropped on target which you do not control.");
			return;
		end
	end
	
	local nodeTargetEffect = nil;
	local custom = draginfo.getCustomData();
	if custom and custom["effect"] then
		nodeTargetEffect = custom["effect"];
	end
	
	onSave(draginfo.getDescription(), draginfo.getNumberData(), rTargetActor, nodeTargetEffect);
end

function onSave(sDesc, nRoll, rActor, nodeTargetEffect)
	-- VALIDATE
	if not rActor and not nodeTargetEffect then
		return;
	end

	-- SETUP
	local isGMOnly = string.match(sDesc, "^%[GM%]") or string.match(sDesc, "^%[TOWER%]") ;

	-- START RESULTS BUILD
	local aResult = { "Saving throw" };
	table.insert(aResult, "[" .. nRoll .. "]");
	table.insert(aResult, "->");
	table.insert(aResult, "[for " .. rActor.sName .. "]");

	-- CHECK FOR DEATH SAVING THROW
	local sDeathSave = string.match(sDesc, "(%[DEATH%])");
	if sDeathSave then
		-- ADD DEATH SAVE NOTE
		table.insert(aResult, sDeathSave);
		
		-- CHECK FOR SPECIAL HANDLING
		local sUpdated = nil;
		if nRoll > 19 then
			-- AUTOMATIC STABILIZATION
			if rActor then
				local sHeal = "[HEAL] Death Save Success [HSV 1] [COST 1]";
				if isGMOnly then
					sHeal = "[GM] " .. sHeal;
				end
				RulesManager.applyDamage(rActor, 0, sHeal);
				sUpdated = "*";
			end
		elseif nRoll > 9 then
			-- DO NOTHING
		else
			-- ADD FAILED DEATH SAVE
			if rActor then
				if rActor.sType == "pc" and rActor.nodeCreature then
					local currentFailedSaves = NodeManager.get(rActor.nodeCreature, "hp.faileddeathsaves", 0);
					currentFailedSaves = currentFailedSaves + 1;
					if currentFailedSaves >= 3 then
						currentFailedSaves = 3;
						sUpdated = " (DEAD)";
					else
						sUpdated = " (" .. currentFailedSaves .. ")";
					end
					NodeManager.set(rActor.nodeCreature, "hp.faileddeathsaves", "number", currentFailedSaves);
					bUpdated = true;
				end
			end
		end
	
		-- DENOTE SUCCESS/FAILURE, AND IF EFFECTS UPDATED
		local sSaveResult = "[";
		if nRoll > 9 then
			sSaveResult = sSaveResult .. "SUCCESS";
		else
			sSaveResult = sSaveResult .. "FAILURE";
		end
		if sUpdated then
			sSaveResult = sSaveResult .. sUpdated;
		end
		sSaveResult = sSaveResult .. "]"
		table.insert(aResult, sSaveResult);

	-- OTHERWISE, IT'S A STANDARD SAVING THROW
	else
		-- IF NO TARGET EFFECT BUT TARGET ENTITY, THEN CHECK FOR SAVE EFFECTS
		if not nodeTargetEffect and rActor then
			if rActor.nodeCT then
				local nodeEffects = rActor.nodeCT.getChild("effects");
				if nodeEffects then
					local bSaveEffectFound = false;
					local nodeSaveEffect = nil;
					for kEffect, nodeEffect in pairs(nodeEffects.getChildren()) do
						if NodeManager.get(nodeEffect, "expiration", "") == "save" then
							if bSaveEffectFound then
								nodeSaveEffect = nil;
							else
								bSaveEffectFound = true;
								nodeSaveEffect = nodeEffect;
							end
						end
					end

					if bSaveEffectFound then
						if nodeSaveEffect then
							nodeTargetEffect = nodeSaveEffect;
						else
							table.insert(aResult, "[MULTIPLE SAVE EFFECTS]");
						end
					end
				end
			end
		end

		-- TARGET EFFECT UPDATE CHECK, IF ANY
		local bUpdated = false;
		if nodeTargetEffect then
			if NodeManager.get(nodeTargetEffect, "expiration", "") == "save" then
				local nodeActor = nodeTargetEffect.getChild("...");

				local sEffectLabel = NodeManager.get(nodeTargetEffect, "label", "");
				table.insert(aResult, string.format("[VS '%s']", sEffectLabel));
				local nEffectMod = NodeManager.get(nodeTargetEffect, "effectsavemod", 0);
				if nEffectMod ~= 0 then
					table.insert(aResult, string.format("[AT %+d]", nEffectMod));
				end
				nRoll = nRoll + nEffectMod;
				if nRoll > 9 then
					ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_EXPIREEFF, {nodeActor.getNodeName(), nodeTargetEffect.getNodeName(), 0});
					bUpdated = true;
				else
					local listEffectComp = EffectsManager.parseEffect(NodeManager.get(nodeTargetEffect, "label", ""));
					local bFailedSaveEffect = false;
					for i = 1, #listEffectComp do
						if listEffectComp[i].type == "FAIL" then
							bFailedSaveEffect = true;
							break;
						end
					end
					if bFailedSaveEffect then
						ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_FAILEDSAVEEFF, {nodeActor.getNodeName(), nodeTargetEffect.getNodeName()});
						bUpdated = true;
					end
				end
			end
		end

		-- DENOTE SUCCESS/FAILURE, AND IF EFFECTS UPDATED
		local sSaveResult = "[";
		if nRoll > 9 then
			sSaveResult = sSaveResult .. "SUCCESS";
		else
			sSaveResult = sSaveResult .. "FAILURE";
		end
		if bUpdated then
			sSaveResult = sSaveResult .. "*";
		end
		sSaveResult = sSaveResult .. "]"
		table.insert(aResult, sSaveResult);
	end
	
	-- DELIVER THE RESULT MESSAGE
	local rMessage = {font = "msgfont"};
	rMessage.text = table.concat(aResult, " ");
	if isGMOnly then
		rMessage.text = "[GM] " .. rMessage.text;
		ChatManager.addMessage(rMessage);
	else
		ChatManager.deliverMessage(rMessage);
	end
end

function onAttackResultDrop(rSourceActor, rTargetActor, draginfo)
	if User.isHost() or OptionsManager.isOption("PATK", "on") then
		-- SETUP
		local sDesc = draginfo.getDescription();
		
		-- Get the target defense
		local nDefense, nAtkEffectsBonus, nDefEffectsBonus = RulesManager.getDefenseValue(rSouceActor, rTargetActor, sDesc);

		-- If we found a defense value, then compare to the number and output
		if nDefense then
			local aResult = {};
			table.insert(aResult, string.format("Attack [" .. draginfo.getNumberData() .. "] ->"));
			table.insert(aResult, "[at " .. rTargetActor.sName .."]");

			if nAtkEffectsBonus ~= 0 then
				table.insert(aResult, string.format("[EFFECTS %+d]", nAtkEffectsBonus));
			end
			if nDefEffectsBonus ~= 0 then
				table.insert(aResult, string.format("[DEF EFFECTS %+d]", nDefEffectsBonus));
			end

			if draginfo.getNumberData() >= nDefense then
				table.insert(aResult, "[HIT]");
			else
				table.insert(aResult, "[MISS]");
			end

			local rMessage = {font = "msgfont"};
			rMessage.text = table.concat(aResult, " ");
			local isGMOnly = string.match(sDesc, "^%[GM%]") or string.match(sDesc, "^%[TOWER%]");
			if isGMOnly then
				rMessage.text = "[GM] " .. rMessage.text;
				ChatManager.addMessage(rMessage);
			else
				ChatManager.deliverMessage(rMessage);
			end
		end
	end
end

function onDamageResultDrop(rSourceActor, rTargetActor, draginfo, adjtype)
	if User.isHost() or OptionsManager.isOption("PDMG", "on") then
		ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYDMG, {draginfo.getNumberData(), draginfo.getDescription(), rTargetActor.sType, rTargetActor.sCreatureNode, rTargetActor.sCTNode});
	end
end

function onEffectDrop(rSourceActor, rTargetActor, draginfo)
	-- GET EFFECT INFORMATION
	local rEffect = RulesManager.decodeEffectFromDrag(draginfo);
	if not rEffect then
		return;
	end

	-- IF NO EXPLICIT EFFECT SOURCE, THEN USE THE SOURCE ACTOR
	if rEffect.sSource == "" then
		if rSourceActor and rSourceActor.sType == "pc" then
			if rSourceActor.nodeCT then
				rEffect.sSource = rSourceActor.sCTNode;
				rEffect.nInit = NodeManager.get(rSourceActor.nodeCT, "initresult", 0);
			end
		end
	end

	-- IF STILL NO SOURCE, THEN USE THE ACTIVE IDENTITY
	if rEffect.sSource == "" then
		local nodeTempCT = nil;
		if User.isHost() then
			nodeTempCT = CombatCommon.getActiveCT();
		else
			nodeTempCT = CombatCommon.getCTFromNode("charsheet." .. User.getCurrentIdentity());
		end
		if nodeTempCT then
			rEffect.sSource = nodeTempCT.getNodeName();
			rEffect.nInit = NodeManager.get(nodeTempCT, "initresult", 0);
		end
	end
	
	-- HANDLE REDUCED CLIENT ACCESS
	if not User.isHost() then
		-- DISABLED EFFECT DROP
		if OptionsManager.isOption("PEFF", "off") then
			return;
		end
		
		-- REPORT ONLY EFFECT DROP
		if OptionsManager.isOption("PEFF", "report") then
			ChatManager.reportEffect(rEffect, rTargetActor.sName);
			return;
		end
	end

	-- DETERMINE TARGET CT NODE
	if not rTargetActor.nodeCT then
		ChatManager.SystemMessage("[ERROR] Effect dropped on target which is not listed in the combat tracker.");
		return;
	end

	-- IF SOURCE AND TARGET HAVE SAME NAME, THEN CLEAR THE SOURCE NAME
	if rEffect.sSource == rTargetActor.sCTNode then
		rEffect.sSource = "";
	end
	
	-- HANDLE THIRD PARTY TARGETING
	local aTargetActors = {};
	local sEffectTargetNode = "";
	if rEffect.targets then
		aTargetActors = rEffect.targets;
		sEffectTargetNode = rTargetActor.sCTNode;
	else
		table.insert(aTargetActors, rTargetActor.sCTNode);
	end

	-- ADD THE EFFECT
	for k, v in pairs(aTargetActors) do
		ChatManager.sendSpecialMessage(ChatManager.SPECIAL_MSGTYPE_APPLYEFF, 
				{v, 
				rEffect.sName or "", 
				rEffect.sExpire or "", 
				rEffect.nSaveMod or 0, 
				rEffect.nInit or 0, 
				rEffect.sSource or "", 
				rEffect.nGMOnly or 0, 
				rEffect.sApply or "", 
				sEffectTargetNode});
	end
end


--
-- END TURN
--

function endTurn(msguser)
	-- Check if the special message user is the same as the owner of the active CT node
	local rActor = getActor("ct", getActiveCT());
	if rActor and rActor.sType == "pc" and rActor.nodeCreature then
		if rActor.nodeCreature.getOwner() == msguser then
			-- Make sure the combat tracker is up on the host
			local wnd = Interface.findWindow("combattracker_window", "combattracker");
			if not wnd then
				local msg = {font = "systemfont"};
				msg.text = "[WARNING] Turns can only be ended when the host combat tracker is open";
				ChatManager.deliverMessage(msg, msguser);
				return;
			end

			-- Everything checks out, so advance the turn
			wnd.list.nextActor();
		end
	end
end


--
-- PARSE CT ATTACK LINE
--

function parseCTAttackLine(sLine)
	-- SETUP
	local rPower = nil;
	
	local nIntroStart, nIntroEnd, sName = string.find(sLine, "([^%(%[]*)[%(%[]?");
	if nIntroStart then
		rPower = {};
		local sTrimmedName, nNameStart, nNameEnd = StringManager.trimString(sName);
		rPower.name = sTrimmedName;
		rPower.range = (string.match(sLine, "%[([MRAC][MRAC]?)%]")) or "";
		rPower.abilities = {};

		local rAbilityName = {};
		rAbilityName.type = "name";
		rAbilityName.name = rPower.name;
		rAbilityName.startpos = nNameStart;
		rAbilityName.endpos = nNameEnd + 1;
		table.insert(rPower.abilities, rAbilityName);
		
		nIndex = nIntroEnd;
		
		local nAttackStart, nAttackEnd, sAttackBonus, sAttackDefense = string.find(sLine, "%(([%+%-]%d+) vs.? (%a*)%)", nIndex);
		if nAttackStart then
			local rAbilityAttack = {};
			rAbilityAttack.startpos = nAttackStart + 1;
			rAbilityAttack.endpos = nAttackEnd;
			rAbilityAttack.type = "attack";
			rAbilityAttack.name = rPower.name;
			rAbilityAttack.range = rPower.range;
			rAbilityAttack.defense = RulesManager.getDefenseInternal(sAttackDefense);
			rAbilityAttack.clauses = {{ stat = {}, mod = tonumber(sAttackBonus) or 0 }};
			table.insert(rPower.abilities, rAbilityAttack);
			
			nIndex = nAttackEnd + 1;
		end
		
		local nDamageStart, nDamageEnd, sDamage, sDamageType = string.find(sLine, "%(([d%+%-%d%s]*%d)%s?([%a,]*)%)", nIndex);
		if nDamageStart then
			local rAbilityDamage = {};
			rAbilityDamage.startpos = nDamageStart + 1;
			rAbilityDamage.endpos = nDamageEnd;
			rAbilityDamage.type = "damage";
			rAbilityDamage.name = rPower.name;
			rAbilityDamage.range = rPower.range;
			rAbilityDamage.clauses = {{ stat = {}, basemult = 0, dicestr = sDamage, critdicestr = "", subtype = sDamageType }};
			table.insert(rPower.abilities, rAbilityDamage);
			
			nIndex = nDamageEnd + 1;
		end
		
		local nUsageStart, nUsageEnd, sUsage = string.find(sLine, "%[(USED)%]", nIndex);
		if not nUsageStart then
			nUsageStart, nUsageEnd, sUsage = string.find(sLine, "%[(R:%d)%]", nIndex);
		end
		if not nUsageStart then
			nUsageStart, nUsageEnd, sUsage = string.find(sLine, "%[([de])%]", nIndex);
		end
		if nUsageStart then
			local rAbilityUsage = {};
			rAbilityUsage.startpos = nUsageStart + 1;
			rAbilityUsage.endpos = nUsageEnd;
			rAbilityUsage.type = "usage";
			table.insert(rPower.abilities, rAbilityUsage);
			
			rPower.usage_val = sUsage;
			rPower.usage_startpos = rAbilityUsage.startpos;
			rPower.usage_endpos = rAbilityUsage.endpos;
			
			nIndex = nUsageEnd + 1;
		end
		
		local nAuraStart, nAuraEnd, sAura = string.find(sLine, "%[(AURA:%d+)%]", nIndex);
		if nAuraStart then
			local rAbilityAura = {};
			rAbilityAura.startpos = nAuraStart + 1;
			rAbilityAura.endpos = nAuraEnd;
			rAbilityAura.type = "aura";
			rAbilityAura.val = tonumber(string.sub(sAura, 6)) or 0;
			table.insert(rPower.abilities, rAbilityAura);
			
			nIndex = nAuraEnd + 1;
		end
	end
	
	return rPower;
end

--
-- ROLL HELPERS
--

function getActor(sActorType, varActor)
	-- GET ACTOR NODE
	local nodeActor = nil;
	if type(varActor) == "string" then
		nodeActor = DB.findNode(varActor);
	elseif type(varActor) == "databasenode" then
		nodeActor = varActor;
	end
	if not nodeActor then
		return nil;
	end

	-- BASED ON ORIGINAL ACTOR NODE, FILL IN THE OTHER INFORMATION
	local rActor = nil;
	if sActorType == "ct" then
		rActor = {};
		rActor.sType = NodeManager.get(nodeActor, "type", "npc");
		rActor.sName = NodeManager.get(nodeActor, "name", "");
		rActor.nodeCT = nodeActor;
		
		local nodeLink = nodeActor.getChild("link");
		if nodeLink then
			local sRefClass, sRefNode = nodeLink.getValue();
			if rActor.sType == "pc" and sRefClass == "charsheet" then
				rActor.nodeCreature = DB.findNode(sRefNode);
			elseif rActor.sType == "npc" and sRefClass == "npc" then
				rActor.nodeCreature = DB.findNode(sRefNode);
			end
		end

	elseif sActorType == "pc" then
		rActor = {};
		rActor.sType = "pc";
		rActor.nodeCreature = nodeActor;
		rActor.nodeCT = getCTFromNode(nodeActor);
		rActor.sName = NodeManager.get(rActor.nodeCT or rActor.nodeCreature, "name", "");

	elseif sActorType == "npc" then
		rActor = {};
		rActor.sType = "npc";
		rActor.nodeCreature = nodeActor;
		
		-- IF ACTIVE CT IS THIS NPC TYPE, THEN ASSOCIATE
		local nodeActiveCT = getActiveCT();
		if nodeActiveCT then
			local nodeLink = nodeActiveCT.getChild("link");
			if nodeLink then
				local sRefClass, sRefNode = nodeLink.getValue();
				if sRefNode == nodeActor.getNodeName() then
					rActor.nodeCT = nodeActiveCT;
				end
			end
		end
		-- OTHERWISE, ASSOCIATE WITH UNIQUE CT, IF POSSIBLE
		if not rActor.nodeCT then
			local nodeTracker = DB.findNode("combattracker");
			if nodeTracker then
				local bMatch = false;
				for keyEntry, nodeEntry in pairs(nodeTracker.getChildren()) do
					local nodeLink = nodeEntry.getChild("link");
					if nodeLink then
						local sRefClass, sRefNode = nodeLink.getValue();
						if sRefNode == nodeActor.getNodeName() then
							if bMatch then
								rActor.nodeCT = nil;
								break;
							end
							
							rActor.nodeCT = nodeEntry;
							bMatch = true;
						end
					end
				end
			end
		end
		
		rActor.sName = NodeManager.get(rActor.nodeCT or rActor.nodeCreature, "name", "");
	end
	
	-- TRACK THE NODE NAMES AS WELL
	if rActor.nodeCT then
		rActor.sCTNode = rActor.nodeCT.getNodeName();
	else
		rActor.sCTNode = "";
	end
	if rActor.nodeCreature then
		rActor.sCreatureNode = rActor.nodeCreature.getNodeName();
	else
		rActor.sCreatureNode = "";
	end
	
	-- RETURN ACTOR INFORMATION
	return rActor;
end

function getActionActors(draginfo)
	-- SETUP
	local rSourceActor = nil;
	local rTargetActor = nil;
	local nodeTargetEffect = nil;

	-- CHECK FOR DRAG INFORMATION
	if draginfo then
		local varCustom = draginfo.getCustomData();

		-- CHECK FOR CUSTOM DATA
		if varCustom then
			-- GET CUSTOM SOURCE ACTOR
			if varCustom["ct"] then
				rSourceActor = getActor("ct", varCustom["ct"]);

			elseif varCustom["pc"] then
				rSourceActor = getActor("pc", varCustom["pc"]);

			elseif varCustom["npc"] then
				rSourceActor = getActor("npc", varCustom["npc"]);
			end
			
			-- GET CUSTOM TARGET ACTOR
			if varCustom["targetct"] then
				local aTargets = StringManager.split(varCustom["targetct"], ChatManager.SPECIAL_MSG_SEP);
				if #aTargets > 1 then
					rTargetActor = {};
					rTargetActor.sType = "multi";
					rTargetActor.aActors = {};
					for keyTarget, sTargetNode in pairs(aTargets) do
						table.insert(rTargetActor.aActors, getActor("ct", sTargetNode));
					end
				elseif #aTargets == 1 then
					rTargetActor = getActor("ct", aTargets[1]);
				end

			elseif varCustom["targetpc"] then
				rTargetActor = getActor("pc", varCustom["targetpc"]);
			end
			
			-- GET CUSTOM TARGET EFFECT
			if varCustom["effect"] then
				nodeTargetEffect = varCustom["effect"];
			end

		-- IF NO CUSTOM DATA, THEN TRY THE SHORTCUT DATA
		else
			-- GET SHORTCUT SOURCE ACTOR
			local sRefClass, sRefNode = draginfo.getShortcutData();
			if sRefClass and sRefNode then
				if sRefClass == "combattracker_entry" then
					rSourceActor = getActor("ct", sRefNode);

				elseif sRefClass == "charsheet" then
					rSourceActor = getActor("pc", sRefNode);

				elseif sRefClass == "npc" then
					rSourceActor = getActor("npc", sRefNode);
				end
			end
			
			-- GET TARGET EFFECT INFO FROM STRING DATA, DEPENDING ON DRAG TYPE
			local dragtype = draginfo.getType();
			if dragtype == "save" or dragtype == "autosave" then
				local sEffectNode = draginfo.getStringData();
				if sEffectNode then
					nodeTargetEffect = DB.findNode(sEffectNode);
				end
			end
		end
	end
	
	-- RESULTS
	return rSourceActor, rTargetActor, nodeTargetEffect;
end

function getSkillRollStructures(sCreatureType, nodeCreature, sSkillName, nSkillMod, sSkillStat)
	local rCreature = getActor(sCreatureType, nodeCreature);

	local rSkill = {};
	rSkill.name = sSkillName;
	rSkill.mod = nSkillMod;

	if sSkillStat or sSkillStat == "" then
		rSkill.stat = sSkillStat;
	else
		for k, v in pairs(DataCommon.skilldata) do
			if k == sSkillName then
				rSkill.stat = v.stat;
			end
		end
	end
	
	return rCreature, rSkill;
end

function getAbilityCheckRollStructures(sCreatureType, nodeCreature, sAbilityStat)
	local rCreature = getActor(sCreatureType, nodeCreature);

	local rAbilityCheck = {};
	rAbilityCheck.stat = sAbilityStat;
	
	return rCreature, rAbilityCheck;
end

function getInitRollStructures(sCreatureType, nodeCreature)
	local rCreature = getActor(sCreatureType, nodeCreature);

	local rInit = {};
	-- PC
	if sCreatureType == "pc" then
		rInit.mod = NodeManager.get(nodeCreature, "initiative.total", 0);
	
	-- NPC / CT
	elseif sCreatureType == "ct" or sCreatureType == "npc" then
		rInit.mod = NodeManager.get(nodeCreature, "init", 0);

	-- DEFAULT
	else
		rInit.mod = 0;
	end

	return rCreature, rInit;
end

function getSaveRollStructures(sCreatureType, nodeCreature)
	local rCreature = getActor(sCreatureType, nodeCreature);
	
	local rSave = {};
	if sCreatureType == "pc" then
		rSave.mod = NodeManager.get(nodeCreature, "defenses.save.total", 0);
	elseif sCreatureType == "ct" then
		rSave.mod = NodeManager.get(nodeCreature, "save", 0);
	elseif sCreatureType == "npc" then
		rSave.mod = tonumber(NodeManager.get(nodeCreature, "saves", "")) or 0;
	else
		rSave.mod = 0;
	end
	
	return rCreature, rSave;
end

